#pragma once

#include <cassert>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>

enum class OperatorType { uminus, uadd, lnot };
enum class ValueType { i32, f32 };
enum class ValueFrom { expression, symbol };

struct SymbolList
{
    using Element = std::variant<int32_t, float>;

    std::vector<std::pair<ValueType, Element>> list;

  public:
    const std::pair<ValueType, Element>& operator[](size_t idx)
    {
        return list[idx];
    }

    size_t insert(std::pair<ValueType, Element> elem)
    {
        auto ret = list.size();
        list.push_back(elem);
        return ret;
    }
};

struct ExpressionList
{
    // 三地址码, 四元组
    using Element = std::tuple<OperatorType,
                               std::optional<std::tuple<ValueFrom, size_t>>,
                               std::optional<std::tuple<ValueFrom, size_t>>,
                               std::optional<std::tuple<ValueFrom, size_t>>>;

    std::vector<Element> list;

  public:
    Element& operator[](size_t idx)
    {
        return list[idx];
    }

    size_t insert(Element elem)
    {
        auto ret = list.size();
        list.push_back(elem);
        return ret;
    }
};

extern SymbolList                                SYMBOL_LIST;
extern ExpressionList                            EXPRESSION_LIST;
extern const std::map<std::string, OperatorType> STR2OP;
extern const std::map<OperatorType, const char*> OP2STR;

class BaseAST {
  public:
    size_t idx;
    ValueFrom from;
    virtual ~BaseAST() = default;
};

class ValueAST : public BaseAST {
  public:
    ValueType type;

    friend std::ostream& operator<<(std::ostream& os, ValueAST& val)
    {
        os << "Value: { ";

        auto& [valtype, valvalue] = SYMBOL_LIST[val.idx];
        switch (valtype) {
            case ValueType::i32: {
                os << "i32, " << std::get<int32_t>(valvalue);
                break;
            }
            case ValueType::f32: assert(false); break;
            default: assert(false); break;
        }
        os << "}";
        return os;
    }
};

class PrimaryExpAST : public BaseAST {
  public:
    enum class PrimaryExpStorage { exp, val };
    PrimaryExpStorage        stores;
    std::unique_ptr<BaseAST> exp;
    std::unique_ptr<BaseAST> val;

    // 关于idx
    // 保存表达式, 即exp有意义时, 为exp->idx
    // 保存值时, 为val->idx

    friend std::ostream& operator<<(std::ostream& os, PrimaryExpAST& pexp);

    void visit(std::ostringstream& decl, std::ostringstream& inst) {}
};

class UnaryExpAST : public BaseAST {
  public:
    // std::unique_ptr<BaseAST> primaryexp;
    // std::pair<std::unique_ptr<std::string>, std::unique_ptr<BaseAST>>
    // op_self;

    enum class UnaryExpStorage { pexp, op_uexp };

    UnaryExpStorage                                   stores;
    std::unique_ptr<BaseAST>                          pexp;
    std::pair<OperatorType, std::unique_ptr<BaseAST>> op_self;

    friend std::ostream& operator<<(std::ostream& os, const UnaryExpAST& uexp)
    {
        os << "UnaryExp { ";

        switch (uexp.stores) {
            case UnaryExpAST::UnaryExpStorage::pexp:
                os << *dynamic_cast<PrimaryExpAST*>(uexp.pexp.get());
                break;
            case UnaryExpAST::UnaryExpStorage::op_uexp: {
                const auto& [op, next_uexp] = uexp.op_self;
                std::cout << OP2STR.at(op) << ", "
                          << *dynamic_cast<UnaryExpAST*>(next_uexp.get());
            }
        }

        // auto pexp    = std::get_if<0>(&uexp.pexp_or_opself);
        // auto op_self = std::get_if<1>(&uexp.pexp_or_opself);
        // if (pexp != nullptr) {
        //     // (*pexp)->Dump();
        //     os << *dynamic_cast<PrimaryExpAST*>(pexp->get());
        // }
        // else {
        //     const auto& [op, uexp] = *op_self;
        //     std::cout << OP2STR.at(op) << ", "
        //               << *dynamic_cast<UnaryExpAST*>(uexp.get());
        // }
        os << "}";
        return os;
    }
};

class ExpAST : public BaseAST {
  public:
    std::unique_ptr<BaseAST> unary_exp;

    friend std::ostream& operator<<(std::ostream& os, const ExpAST& exp)
    {
        os << "Exp {";
        os << *dynamic_cast<UnaryExpAST*>(exp.unary_exp.get());
        os << "}";
        return os;
    }
};

// 规避掉类型不完全问题
inline std::ostream& operator<<(std::ostream& os, PrimaryExpAST& pexp)
{
    os << "PrimaryExp { ";
    // auto exp = std::get_if<0>(&pexp.exp_or_val);
    // auto val = std::get_if<1>(&pexp.exp_or_val);
    switch (pexp.stores) {
        case PrimaryExpAST::PrimaryExpStorage::exp:
            os << *dynamic_cast<ExpAST*>(pexp.exp.get());
            break;

        case PrimaryExpAST::PrimaryExpStorage::val:
            os << *dynamic_cast<ValueAST*>(pexp.val.get());
            break;
    }
    os << "}";
    return os;
}

class StmtAST : public BaseAST {
  public:
    // std::unique_ptr<BaseAST> exp;
    std::vector<std::unique_ptr<BaseAST>> exps;

    friend std::ostream& operator<<(std::ostream& os, const StmtAST& stmt)
    {
        os << "Statement { ";
        for (auto& exp : stmt.exps) {
            os << *dynamic_cast<ExpAST*>(exp.get());
        }

        os << "}";
        return os;
    }
};

class BlockAST : public BaseAST {
  public:
    std::vector<std::unique_ptr<BaseAST>> statements;
    // std::unique_ptr<std::string> name;

    friend std::ostream& operator<<(std::ostream& os, const BlockAST& blk)
    {
        os << "Block { ";
        for (auto& statement : blk.statements) {
            os << *dynamic_cast<StmtAST*>(statement.get());
        }

        os << "}";
        return os;
    }
};

class FuncDefAST : public BaseAST {
  public:
    std::unique_ptr<std::string>          func_type;
    std::unique_ptr<std::string>          ident;
    std::vector<std::unique_ptr<BaseAST>> blocks;

    friend std::ostream& operator<<(std::ostream& os, const FuncDefAST& fdef)
    {
        os << "FuncDef { " << *fdef.func_type << ", " << *fdef.ident << ", ";
        for (auto& block : fdef.blocks) {
            os << *dynamic_cast<BlockAST*>(block.get());
        }
        os << "}";
        return os;
    }
};

class CompUnitAST : public BaseAST {
  public:
    // std::unique_ptr<BaseAST> func_def;
    std::vector<std::unique_ptr<BaseAST>> func_defs;

    friend std::ostream& operator<<(std::ostream& os, const CompUnitAST& cu)
    {
        os << "CompUnit { ";
        for (auto& func : cu.func_defs) {
            os << *dynamic_cast<FuncDefAST*>(func.get());
        }
        os << "}";
        return os;
    }
};